﻿using JESAI.Core.Websocket.Client.Messages;
using JESAI.Core.Websocket.Client.Models;
using System;
using System.Buffers;
using System.Collections.Generic;
using System.Net.WebSockets;
using System.Text;
using System.Threading.Tasks;
using System.IO;

namespace JESAI.Core.Websocket.Client
{
    /// <summary>
    /// 一个简单的websocket客户端，内置重新连接和错误处理功能
    /// </summary>
    public interface IWebsocketClient : IDisposable
    {
        /// <summary>
        /// 向websocket通道发送短信。
        /// 它将消息插入队列，并在另一个线程上完成实际发送
        /// </summary>
        /// <param name="message">要发送的文本消息</param>
        /// <returns>如果消息已写入队列，则返回true</returns>
        bool Send(string message);

        /// <summary>
        /// 向websocket通道发送二进制消息。
        /// 它将消息插入队列，并在另一个线程上完成实际发送
        /// </summary>
        /// <param name="message">要发送的二进制消息</param>
        /// <returns>如果消息已写入队列，则返回true</returns>
        bool Send(byte[] message);

        /// <summary>
        /// 向websocket通道发送二进制段消息。
        /// 它将消息插入队列，并在另一个线程上完成实际发送
        /// </summary>
        /// <param name="message">要发送的二进制段消息</param>
        /// <returns>如果消息已写入队列，则返回true</returns>
        bool Send(ArraySegment<byte> message);

        /// <summary>
        /// 向websocket通道发送二进制序列消息
        /// 它将消息插入队列，并在另一个线程上完成实际发送   
        /// </summary>
        /// <param name="message">要发送的二进制序列消息</param>
        /// <returns>如果消息已写入队列，则返回true</returns>
        bool Send(ReadOnlySequence<byte> message);

        /// <summary>
        /// 向websocket通道发送文本消息。
        /// 它不使用发送队列，
        /// 在完整的.NET Framework平台上同时发送两条消息时，请注意此问题
        /// </summary>
        /// <param name="message">要发送的文本消息</param>
        Task SendInstant(string message);

        /// <summary>
        /// 向websocket通道发送二进制消息。
        /// 它不使用发送队列，
        /// 在完整的.NET Framework平台上同时发送两条消息时，请注意此问题
        /// </summary>
        /// <param name="message">要发送的消息</param>
        Task SendInstant(byte[] message);

        /// <summary>
        /// 将已转换的文本消息发送到websocket通道。
        /// 使用此方法可避免文本消息的双重序列化。
        /// 它将消息插入队列，并在另一个线程上完成实际发送
        /// </summary>
        /// <param name="message">要发送的消息</param>
        /// <returns>如果消息已写入队列，则返回true</returns>
        bool SendAsText(byte[] message);

        /// <summary>
        /// 将已转换的短信发送到websocket通道。
        /// 使用此方法可避免文本消息的双重序列化。
        /// 它将消息插入队列，并在另一个线程上完成实际发送
        /// </summary>
        /// <param name="message">要发送的消息</param>
        /// <returns>如果消息已写入队列，则返回true</returns>
        bool SendAsText(ArraySegment<byte> message);

        /// <summary>
        ///将已转换的短信发送到websocket通道。
        /// 使用此方法可避免文本消息的双重序列化。
        /// 它将消息插入队列，并在另一个线程上完成实际发送
        /// </summary>
        /// <param name="message">要发送的消息</param>
        /// <returns>如果消息已写入队列，则返回true</returns>
        bool SendAsText(ReadOnlySequence<byte> message);

        /// <summary>
        /// 流式传输/发布假消息（通过可观察到的“MessageReceived”）。
        /// 用于测试目的，模拟服务器消息。 
        /// </summary>
        /// <param name="message">要流式传输的消息</param>
        void StreamFakeMessage(ResponseMessage message);

        /// <summary>
        /// 远程服务器地址。更改后重新连接。
        /// </summary>
        Uri Url { get; set; }

        /// <summary>
        /// 接收到的消息流（原始格式）
        /// </summary>
        IObservable<ResponseMessage> MessageReceived { get; }

        /// <summary>
        /// 重新连接事件流（在新连接后触发）
        /// </summary>
        IObservable<ReconnectionInfo> ReconnectionHappened { get; }

        /// <summary>
        /// 断开连接事件流（在连接丢失后触发）
        /// </summary>
        IObservable<DisconnectionInfo> DisconnectionHappened { get; }

        /// <summary>
        /// 如果没有来自服务器的消息，在重新连接之前等待多长时间的时间范围。
        /// 设置null可禁用此功能。
        /// 默认值：1分钟
        /// </summary>
        TimeSpan? ReconnectTimeout { get; set; }

        /// <summary>
        /// 如果上次重新连接失败，在重新连接之前等待多长时间的时间范围。
        /// 设置null可禁用此功能。
        /// 默认值：1分钟
        /// </summary>
        TimeSpan? ErrorReconnectTimeout { get; set; }

        /// <summary>
        /// 如果连接因瞬态错误而丢失，在重新连接之前等待多长时间的时间范围。
        /// 设置null可禁用此功能。
        /// 默认值：空/禁用（立即）
        /// </summary>
        TimeSpan? LostReconnectTimeout { get; set; }

        /// <summary>
        /// 启用或禁用重新连接功能（默认启用）
        /// </summary>
        bool IsReconnectionEnabled { get; set; }

        /// <summary>
        /// 获取或设置当前websocket客户端实例的名称。
        /// 用于日志记录目的（以防您使用更多并行websocket客户端并希望区分它们）
        /// </summary>
        string? Name { get; set; }

        /// <summary>
        /// 如果Start（）方法至少被调用过一次，则返回true。如果未启动或处理，则为False
        /// </summary>
        bool IsStarted { get; }

        /// <summary>
        /// 如果客户端正在运行并连接到服务器，则返回true
        /// </summary>
        bool IsRunning { get; }

        /// <summary>
        /// 启用或禁用从二进制到字符串的文本消息转换（通过“MessageEncoding”属性）。
        /// 默认值：true
        /// </summary>
        bool IsTextMessageConversionEnabled { get; set; }

        /// <summary>
        /// 启用或禁用自动 <see cref="MemoryStream.Dispose(bool)"/> 的 <see cref="MemoryStream"/> 
        /// 发送数据后（仅适用于二进制响应）。
        /// 将值设置为false允许您直接访问流。
        /// <warning>但是，请记住，您需要自己处理处置。</warning>
        /// 默认值：true
        /// </summary>
        bool IsStreamDisposedAutomatically { get; set; }

        /// <summary>
        /// 文本消息的目标编码
        /// </summary>
        Encoding? MessageEncoding { get; set; }

        /// <summary>
        /// 基础客户端，每次重新连接后都会发生变化，请谨慎使用
        /// </summary>
        ClientWebSocket? NativeClient { get; }

        /// <summary>
        /// 终止websocket连接并清理所有内容
        /// </summary>
        void Dispose();

        /// <summary>
        /// 开始在后台线程上监听websocket流。
        /// 如果发生连接错误，它不会抛出异常。
        /// 仅通过“DisconnectionHappened”流式传输消息并记录。
        /// </summary>
        Task Start();

        /// <summary>
        /// 开始在后台线程上监听websocket流。
        /// 如果发生连接错误，它不会抛出异常。
        /// 仅通过“DisconnectionHappened”流式传输消息并记录。
        /// </summary>
        /// <param name="startBackgroundThreadSendText"> 是否启动后台线程发送文本消息 </param>
        /// <param name="startBackgroundThreadSendBinary"> 是否启动后台线程发送二进制消息 </param>
        /// <returns></returns>
        Task Start(bool startBackgroundThreadSendText, bool startBackgroundThreadSendBinary);

        /// <summary>
        /// 开始在后台线程上监听websocket流。
        /// 如果发生连接错误，它会抛出异常。
        /// 快速失败的方法。 
        /// </summary>
        Task StartOrFail();

        /// <summary>
        /// 开始在后台线程上监听websocket流。
        /// 如果发生连接错误，它会抛出异常。
        /// 快速失败的方法。 
        /// </summary>
        /// <param name="startBackgroundThreadSendText"> 是否启动后台线程发送文本消息 </param>
        /// <param name="startBackgroundThreadSendBinary"> 是否启动后台线程发送二进制消息 </param>
        /// <returns></returns>
        Task StartOrFail(bool startBackgroundThreadSendText, bool startBackgroundThreadSendBinary);

        /// <summary>
        /// 使用自定义关闭代码停止/关闭websocket连接。
        /// 方法不抛出异常，只记录异常并将客户端标记为关闭。 
        /// </summary>
        /// <returns>如果关闭成功启动，则返回true</returns>
        Task<bool> Stop(WebSocketCloseStatus status, string statusDescription);

        /// <summary>
        /// 使用自定义关闭代码停止/关闭websocket连接。
        /// 方法可能会抛出异常，但客户端仍被标记为关闭。
        /// </summary>
        /// <returns>如果关闭成功启动，则返回true</returns>
        Task<bool> StopOrFail(WebSocketCloseStatus status, string statusDescription);

        /// <summary>
        /// 强制重新连接。
        /// 关闭当前websocket流并执行与服务器的新连接。
        /// 如果发生连接错误，它不会抛出异常，但会尝试无限期重新连接。
        /// </summary>
        Task Reconnect();

        /// <summary>
        /// 强制重新连接。
        /// 关闭当前websocket流并执行与服务器的新连接。
        /// 如果发生连接错误，它会抛出异常，并且不会执行任何其他重新连接尝试。
        /// </summary>
        Task ReconnectOrFail();
    }
}
